/*	
http.c	Http Download Code
Copyright (C) 2009 Haibo Hu <aquarius23@126.com> & <haibo.hu@archermind.com>

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "http.h"

#ifndef INFINITE
#define INFINITE	0xFFFFFFFF
#endif

static int wait_read(int s)
{
	int state;
	fd_set rfdset;
	struct timeval tv;
	tv.tv_sec = INFINITE;
	tv.tv_usec = 0;
	FD_ZERO(&rfdset);
	FD_SET(s, &rfdset);
	state = select(s + 1, &rfdset, NULL, NULL, &tv);
	if(FD_ISSET(s, &rfdset))
		return state;
	else
		return state;
}

static void chunk_size_skip(struct http_task *task)
{
	if(task->valid_size)
	{
		if(*task->pos == '\r')
		{
			task->pos++;
			task->valid_size--;
		}
	}
	if(task->valid_size)
	{
		if(*task->pos == '\n')
		{
			task->pos++;
			task->valid_size--;
		}
	}
}

static unsigned int chunk_size(char *p)
{
	char c;
	unsigned int size = 0;
	if((*p == '0') && (*(p + 1) == '\r'))
		return 0;
	do
	{
		c = *p;
		if((c >= '0') && (c <= '9'))
			c -= '0';
		else if((c >= 'a') && (c <= 'f'))
			c = c - 'a' + 10;
		else if((c >= 'A') && (c <= 'F'))
			c = c - 'A' + 10;
		else
			break;
		size = (size << 4) | c; 
		p++;
	} while(1);
	return size;
}

static short verify_http_head(char *buf, struct http_task *task)
{
	int stat;
	char *p;
	p = buf + 9;
	stat = atoi(p);
	if(stat == 206)
		stat = 200;
	if(stat == 200)
	{
		p = strstr(buf, "\r\n\r\n");
		task->pos = p + 4;
		task->valid_size -= (task->pos - buf);
		*p = 0;
		if((p = strstr(buf, "Content-Length")) || (p = strstr(buf, "Content-length:")))
		{
			p+= 16;
			task->size = atoi(p);
			task->remain = task->size;
			task->state = HTTP_NORMAL;
		}
		else if((p = strstr(buf, "Transfer-Encoding: chunked")))
		{
			task->size = 0;
			task->remain = 0;
			task->state = HTTP_CHUNKED;
		}
		else if((p = strstr(buf, "HTTP/1.0 200")))
		{
			task->size = 0;
			task->remain = 0;
			task->state = HTTP_10_NO_SIZE;
		}
		else
			stat = 404;
	}
	return stat;
}

static int parse_url(char *url, char * res, int flag) {
	char *p;
	char *pn;
	int err = 0;
	int len;

	if((!url) || (!(*url)) || (!res))
		return -1;

	if(flag == FILENAME)
	{
		len = strlen(url);
		p = url + len;
		do
		{
			p--;
			if(p < url)
			{
				err = -1;
				break;
			}
		} while(*p != '/');

		if(err != -1)
		{
			if((*(p-1) == '/') || (*(p+1) == 0))
				strcpy(res, "index.html");
			else
				strcpy(res, ++p);
		}
	}
	else
	{
		if((p = strstr(url, "http://")))
		{
			p+= 7;
			if(flag == PORTNAME)
				strcpy(res, "80");
		}
		else if((p = strstr(url, "ftp://")))
		{
			p+= 6;
			if(flag == PORTNAME)
				strcpy(res, "21");
		}
		else
		{
			p = url;
			if(flag == PORTNAME)
				strcpy(res, "80");
		}

		pn = p;
		while(*pn)
		{
			if(*pn == ':')
			{
				if(flag == PORTNAME)
				{
					pn++;
					p = pn;
					while(*p)
					{
						if(*p == '/')
							break;
						p++;
					}
					if(*p)
					{
						strncpy(res, pn, p-pn);
						res[p-pn] = 0;
					}
					else
						strcpy(res, pn);
					break;
				}
				else if(flag == SITENAME)
					break;
			}

			if(*pn == '/')
				break;

			pn++;
		}

		if(flag == PATHNAME)
			strcpy(res, pn);
		else if(flag == SITENAME)
		{
			strncpy(res, p, pn-p);
			res[pn-p] = 0;
		}
	}
	return err;
}		 

static void showhost(struct hostent *host) {
	char **alias;
	char *addr_name;
	if(!host){
		printf("never find host : \n");
		return;
	}
	if(host->h_name)
		printf("Official host name : %s\n", host->h_name);
	printf("Name aliases : \n");
	for(alias = host->h_aliases; *alias; alias++)
		printf("%s \n",*alias);
	printf("IP address:\n");
	for(alias = host->h_addr_list; *alias; alias++) {
		addr_name = (char *)inet_ntoa(*(struct in_addr *)(*alias));
		if(addr_name)
			printf("%s \n",addr_name);
	}
}

#ifdef WIN32
static int inet_aton(const char *string, struct in_addr *addr)
{
	addr->S_un.S_addr = inet_addr(string);
	if(addr->S_un.S_addr == -1) 
		return 0;
	return 1;
}
#endif

static int connect_host(char *url) {
	int sd;
	char temp[512];
	struct sockaddr_in pin;
	struct hostent *h;
	struct in_addr host_addr;


	memset(&pin, 0, sizeof(pin));
	parse_url(url, temp, SITENAME);
	if(inet_aton(temp, &host_addr)) {
		/**((int *)(&host_addr)) = htonl(*(int *)(&host_addr));*/
		pin.sin_addr = host_addr;
		goto ip_ok;
	}
	else if(!(h = gethostbyname(temp))) {
		printf("can't get host\n");
		return -1;
	}
#ifdef DEBUG
	showhost(h);
#endif

	pin.sin_addr = *((struct in_addr *)h->h_addr);
ip_ok:
	pin.sin_family = AF_INET;
	parse_url(url, temp, PORTNAME);
	pin.sin_port = htons(atoi(temp));

	if((sd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
		printf("can't create socket.\n");
		return -1;
	}
	if(connect(sd, (struct sockaddr*)&pin, sizeof(pin)) == -1) {
		printf("can't connect web.\n");
		return -1;
	}
	return sd;
}	

struct http_task *create_http_task(char *url, HTTP_CALLBACK pCallBack, void *arg)
{
	struct http_task *task = malloc(sizeof(struct http_task));
	if(!task)
		return NULL;
	memset(task, 0, sizeof(struct http_task));
	task->url = malloc(strlen(url) + 1);
	if(!task->url)
	{
		free(task);
		return NULL;
	}
	strcpy(task->url, url);
	task->callback = pCallBack;
	task->arg = arg;
	return task;
}

static int connect_http_server(struct http_task *task)
{
	task->sd = connect_host(task->url);
	return task->sd;
}

void cancel_http_download(struct http_task *task)
{
	task->cancel = 1;
	close(task->sd);
}

void free_http_task(struct http_task *task)
{
	if(task->sd)
		close(task->sd);
	free(task->url);
	free(task);
}

int start_http_download(struct http_task *task)
{
	int state;
	char path[256];
	char host[128];
	char cmd[512];

	connect_http_server(task);
	if(task->sd <= 0)
		return -1;
	parse_url(task->url, path, PATHNAME);
	parse_url(task->url, host, SITENAME);
	sprintf(cmd, "GET %s HTTP/1.1\r\nAccept: */*\r\nAccept-Language:zh-cn\r\nAccept: gzip,deflate\r\nUser-Agant: Aquarius23\r\nHost: %s\r\nConnection: Keep-Alive\r\n\r\n", path, host);
	task->pos = task->buf;
	if(send(task->sd, cmd, strlen(cmd), 0) != (int)strlen(cmd))
	{
		printf("send http get command error.\n");
		return -1;
	}

	do
	{
		if(task->cancel)
			break;
		state = wait_read(task->sd);
		if(state == 0)
			continue;
		else if(state < 0)
		{
			if(task->cancel == 1)
			{
				task->sd = 0;
				return 0;
			}
			else
				return -1;
		}

		do 
		{
			task->valid_size += recv(task->sd, task->pos, BUFSIZE, 0);
			task->pos = task->buf;
			if(task->valid_size < 0)
			{
				printf("read http response error.\n");
				return -1;
			}
			else if(task->valid_size == 0)
			{
				if(task->state == HTTP_10_NO_SIZE)
					task->cancel = 2;
				break;
			}

			if(task->state == HTTP_UNKNOW)
			{
				state = verify_http_head(task->buf, task);
				if(state == 304) /*not modify */
				{
					printf("not modify\n");
					return 0;
				}
				if(state != 200)
					return -1;
			}

			if(task->state == HTTP_NORMAL)
			{
				task->callback(task->pos, task->valid_size, task->arg);
				task->remain -= task->valid_size;
				task->valid_size = 0;
				task->pos = task->buf;
				if(!task->remain)
				{
					task->cancel = 2;
					break;
				}
			}
			else if(task->state == HTTP_10_NO_SIZE)
			{
				task->callback(task->pos, task->valid_size, task->arg);
				task->valid_size = 0;
				task->pos  = task->buf;
			}
			else if(task->state == HTTP_CHUNKED)
			{
				if(task->chunked_extra)
				{
					task->chunked_extra = 0;
					task->pos++;
					task->valid_size--;
				}
				do
				{
					int real_size;
					if(task->size == 0)
					{
						char *p;
						int count, valid = 0;
						chunk_size_skip(task);
						p = task->pos;
						count = task->valid_size;
						while(count--)
						{
							if(*p++ == '\r')
							{
								valid = 1;
								break;
							}
						}
						if(!valid)
						{
							/* chunk size string like abc(!d\r\n) */
							memmove(task->buf, task->pos, task->valid_size);
							task->pos = task->buf + task->valid_size;
							break;
						}
						task->size = chunk_size(task->pos);
						task->remain = task->size;
						task->pos +=  task->valid_size - count;
						task->valid_size = count;
						if(task->size == 0)
						{
							task->state = HTTP_CHUNKED_END;
							break;
						}
						if(!task->valid_size)
						{
							/* chunk size string like abcd\r(!\n) */
							task->chunked_extra = 1;
							task->pos = task->buf;
							break;
						}
						task->valid_size--;
						task->pos++;
					}
					real_size = (task->valid_size < task->remain)? task->valid_size: task->remain;
					task->callback(task->pos, real_size, task->arg);
					task->valid_size -= real_size;
					task->remain -= real_size;
					if(!task->remain)
						task->size = 0;
					task->pos += real_size;
					if(!task->valid_size)
					{
						task->pos = task->buf;
						break;
					}
				} while(1);
				if(task->state == HTTP_CHUNKED_END)
				{
					task->cancel = 2;
					break;
				}
			}
		} while(1);
	} while(1);

	if(task->cancel == 1)
		return 0;
	return 1;
}
